// Copyright 2014 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/ic/call-optimization.h"
#include "src/objects-inl.h"

namespace v8 {
namespace internal {

    CallOptimization::CallOptimization(Isolate* isolate, Handle<Object> function)
    {
        constant_function_ = Handle<JSFunction>::null();
        is_simple_api_call_ = false;
        expected_receiver_type_ = Handle<FunctionTemplateInfo>::null();
        api_call_info_ = Handle<CallHandlerInfo>::null();
        if (function->IsJSFunction()) {
            Initialize(isolate, Handle<JSFunction>::cast(function));
        } else if (function->IsFunctionTemplateInfo()) {
            Initialize(isolate, Handle<FunctionTemplateInfo>::cast(function));
        }
    }

    Context CallOptimization::GetAccessorContext(Map holder_map) const
    {
        if (is_constant_call()) {
            return constant_function_->context()->native_context();
        }
        JSFunction constructor = JSFunction::cast(holder_map->GetConstructor());
        return constructor->context()->native_context();
    }

    bool CallOptimization::IsCrossContextLazyAccessorPair(Context native_context,
        Map holder_map) const
    {
        DCHECK(native_context->IsNativeContext());
        if (is_constant_call())
            return false;
        return native_context != GetAccessorContext(holder_map);
    }

    Handle<JSObject> CallOptimization::LookupHolderOfExpectedType(
        Handle<Map> object_map, HolderLookup* holder_lookup) const
    {
        DCHECK(is_simple_api_call());
        if (!object_map->IsJSObjectMap()) {
            *holder_lookup = kHolderNotFound;
            return Handle<JSObject>::null();
        }
        if (expected_receiver_type_.is_null() || expected_receiver_type_->IsTemplateFor(*object_map)) {
            *holder_lookup = kHolderIsReceiver;
            return Handle<JSObject>::null();
        }
        if (object_map->has_hidden_prototype()) {
            JSObject raw_prototype = JSObject::cast(object_map->prototype());
            Handle<JSObject> prototype(raw_prototype, raw_prototype->GetIsolate());
            object_map = handle(prototype->map(), prototype->GetIsolate());
            if (expected_receiver_type_->IsTemplateFor(*object_map)) {
                *holder_lookup = kHolderFound;
                return prototype;
            }
        }
        *holder_lookup = kHolderNotFound;
        return Handle<JSObject>::null();
    }

    bool CallOptimization::IsCompatibleReceiver(Handle<Object> receiver,
        Handle<JSObject> holder) const
    {
        DCHECK(is_simple_api_call());
        if (!receiver->IsHeapObject())
            return false;
        Handle<Map> map(HeapObject::cast(*receiver)->map(), holder->GetIsolate());
        return IsCompatibleReceiverMap(map, holder);
    }

    bool CallOptimization::IsCompatibleReceiverMap(Handle<Map> map,
        Handle<JSObject> holder) const
    {
        HolderLookup holder_lookup;
        Handle<JSObject> api_holder = LookupHolderOfExpectedType(map, &holder_lookup);
        switch (holder_lookup) {
        case kHolderNotFound:
            return false;
        case kHolderIsReceiver:
            return true;
        case kHolderFound:
            if (api_holder.is_identical_to(holder))
                return true;
            // Check if holder is in prototype chain of api_holder.
            {
                JSObject object = *api_holder;
                while (true) {
                    Object prototype = object->map()->prototype();
                    if (!prototype->IsJSObject())
                        return false;
                    if (prototype == *holder)
                        return true;
                    object = JSObject::cast(prototype);
                }
            }
            break;
        }
        UNREACHABLE();
    }

    void CallOptimization::Initialize(
        Isolate* isolate, Handle<FunctionTemplateInfo> function_template_info)
    {
        if (function_template_info->call_code()->IsUndefined(isolate))
            return;
        api_call_info_ = handle(
            CallHandlerInfo::cast(function_template_info->call_code()), isolate);

        if (!function_template_info->signature()->IsUndefined(isolate)) {
            expected_receiver_type_ = handle(FunctionTemplateInfo::cast(function_template_info->signature()),
                isolate);
        }
        is_simple_api_call_ = true;
    }

    void CallOptimization::Initialize(Isolate* isolate,
        Handle<JSFunction> function)
    {
        if (function.is_null() || !function->is_compiled())
            return;

        constant_function_ = function;
        AnalyzePossibleApiFunction(isolate, function);
    }

    void CallOptimization::AnalyzePossibleApiFunction(Isolate* isolate,
        Handle<JSFunction> function)
    {
        if (!function->shared()->IsApiFunction())
            return;
        Handle<FunctionTemplateInfo> info(function->shared()->get_api_func_data(),
            isolate);

        // Require a C++ callback.
        if (info->call_code()->IsUndefined(isolate))
            return;
        api_call_info_ = handle(CallHandlerInfo::cast(info->call_code()), isolate);

        if (!info->signature()->IsUndefined(isolate)) {
            expected_receiver_type_ = handle(FunctionTemplateInfo::cast(info->signature()), isolate);
        }

        is_simple_api_call_ = true;
    }
} // namespace internal
} // namespace v8
